package chapter2_nio.obj;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class Server {
    public static void main(String[] args) throws IOException {
        Server server=new Server(9998);
        server.service();
    }
    private Selector selector;
    //创建ServerSocketChannel对象，并将其注册到selector选择器上
    public Server(int port) throws IOException {//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        //用Selector.open()可以获得一个新的选择器实例(我的理解是和Server端的selector是同一个对象)
        selector=Selector.open();
        ServerSocketChannel ssChannel=ServerSocketChannel.open();//获取ServerSocketChannel实例
        ssChannel.configureBlocking(false);//将ServerSocketChannel配置为非阻塞模式
        ssChannel.bind(new InetSocketAddress("localhost",port));
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //将ssChannel注册到selector选择器上，注册的同时指定此ssChannel的作用为"接受连接"的作用。SelectionKey.OP_ACCEPT（接受连接操作）
        // 这样，当有连接请求到达时，选择器会通知该通道可以进行接受此连接
        System.out.println("服务端开启端口：" + port +"等待客户端链接....");
    }

    void closeChannel(SelectionKey key)throws Exception{
        key.channel().close();
        key.cancel();
        System.out.println("会话结束，服务端关闭连接");
    }

    /**
     * 从selector中获取所有的selectionKey判断判断它是否是可连接的如果是,
     *    就接收客户端连接得到一个SocketChannel并将其注册到selector中。
     * 如果不是可连接的就调用session()方法进行数据传输。
     */
    public void service(){//√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
        while(true){
            try{
                //选择器的select()用于等待并选择一个可用的通道，以便进行读取或写入或其他操作(比如连接操作)。
                // 它会阻塞当前线程，直到有任何一个通道变为可读、可写或连接就绪或发生其他感兴趣的事件时sector选择器就会得到通知，
                // 此方法也会向下继续运行。
                // 在返回时，会选择一个或多个通道，并将它们的状态设置为已选择，以便应用程序可以进行相应的操作。
                selector.select();//会一直等待..........(有attachement对象准备好接收数据或准备好发送数据，此select方法都会有返回值)
                //获取selector中触发了对应事件的selectionKey组成的集合，并处理集合中每个selectionKey对应的SocketChannel的事件，
                // 处理完一个就将其对应的selectionKey从Set集合中移除(否则下一次还会将这个事件的SelectionKey再处理一遍)
                Set<SelectionKey> selectionKeySet=selector.selectedKeys();
                System.out.println("get--selectionKeySet:"+selectionKeySet.size());
                Iterator<SelectionKey> iterator=selectionKeySet.iterator();
                while(iterator.hasNext()){
                    SelectionKey key=iterator.next();
                    if(key.isAcceptable()){//判断这个key的状态是否可接受连接
                        ServerSocketChannel ssChannel=(ServerSocketChannel)key.channel();
                        //1.接受连接
                        //从ServerSocketChannel得到连接并返回一个可用的SocketChannel
                        SocketChannel channel=ssChannel.accept();
                        System.out.println("客户端链接成功，连接信息：" + channel+"客户端可以和服务端进行数据传输了...");
                        channel.configureBlocking(false);//将SocketChannel配置为非阻塞模式
                        registerOnConnected(channel,key.selector());//将获得的SocketChannel注册到selector选择器上
                    }else{//如果这个key不能接受连接，就进行数据传输
                        session(key);
                    }
                    //获取selector中的所有selectionKey组成的集合，并处理集合中每个selectionKey对应的SocketChannel的事件，
                    // 处理完一个就将其对应的selectionKey从Set集合中移除(选择器上的selectionKey还在， 没有被删除<只是删除了Set集合中的元素>)
                    System.out.println("before--remove:"+selectionKeySet.size());
                    iterator.remove();
                    System.out.println("remove:"+selectionKeySet.size());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    //√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
    public void registerOnConnected(SocketChannel channel, Selector selector) throws ClosedChannelException {
        System.out.println("模拟将SocketChannel注册到Selector上,attachment是ReceiverUtil");
        //2.注册一个ReceiverUtil对象到selector上(当下一次用selectedKeys获取触发事件的selectionKey时，
        // 获得此SocketChannel对应的SelectionKey的注册信息<selectionKey对应的attachment对象是ReceiverUtil>)

        channel.register(selector,SelectionKey.OP_READ, new ReceiverUtil(0, Student.class));
        //                选择器， 设置SocketChannel可以处理读操作， 采用ReveiverUtil类来处理数据(这个参数叫attachment对象)
        /*Set<SelectionKey> set=selector.selectedKeys();
        for (SelectionKey key: set){
            System.out.println(key+"-"+111+"stepIn"+key.attachment());
            if(key.attachment() instanceof  ReceiverUtil){
                System.out.println( "stepIndex:"+ ((ReceiverUtil)key.attachment()).stepIndex  );
            }
        }*/
    }




    //√ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √ √
    public void session(SelectionKey key) throws Exception {
        // 服务端会话操作：读、写、写、读(列表)
        SocketChannel channel = (SocketChannel) key.channel();
        if (key.isReadable()) {
            ReceiverUtil receiver = (ReceiverUtil)key.attachment();
            if (receiver.recvFully(channel)){
                Object obj = receiver.getObject();
                if (obj.getClass().equals(Student.class)) {
                    System.out.println("接收单个学生: " + obj);
                }
                /*if (obj instanceof List){
                    System.out.println("接收学生列表：" + obj);
                }*/
                if (receiver.getStepIndex()==0) {
                    Student stu2=new Student(102, "孙小婧", 'F',3.76 );
                    channel.register(key.selector(), SelectionKey.OP_WRITE, new SenderUtil(receiver.getStepIndex()+1, stu2));
                }
                if (receiver.getStepIndex()==3){
                    this.closeChannel(key);
                }
            }
        }else {
            SenderUtil sender = (SenderUtil)key.attachment();
            if (sender.sendFully(channel)){
                System.out.println("发送单个学生: " + sender.getObject());
                //根据情况设定Sender的信息
                if (sender.getStepIndex()==1) {
                    Student stu3=new Student(103, "Peter Kevin", 'M',3.58 );
                    //因为允许的操作相同，所以不用重复注册，只需修改attachment对象
                    key.attach(new SenderUtil(sender.getStepIndex()+1, stu3));
                }
                if (sender.getStepIndex()==2) {
                    channel.register(key.selector(), SelectionKey.OP_READ, new ReceiverUtil(sender.getStepIndex()+1, List.class));
                }
            }
        }

    }




}



//本案例的Server中用到的关键类:
//1.ServerSocketChannel: 类似于之前的ServerSocket，它负责启用一个端口并监听，
//                       当有客户端连接时，可通过accept()方法得到一个SocketChannel实例来与客户端进行数据通信。
//2.SocketChannel: 类似于之前的Socket，它负责与客户端进行数据通信。
//3.Selector: 选择器，ServerSocketChannel需要向Selector选择器注册并分配一个SelectionKey
//            (通过SelectionKey找到ServerSocketChannel或其他SocketChannel)。
//            它负责等待有就绪的通道并选择一个已经就绪的通道，以便进行读取或写入或其他操作(比如连接操作)。



